/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[欢迎加盟知启蒙，一起邂逅框架梦]
 * 
 * https://www.zhiqim.com/gitcan/zhiqim/zhiqim_account.htm
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.zhiqim.account.api.pay;

import java.util.HashMap;
import java.util.Map;

import org.zhiqim.account.ZacConstants;
import org.zhiqim.kernel.Global;
import org.zhiqim.kernel.Servicer;
import org.zhiqim.kernel.config.Group;
import org.zhiqim.kernel.json.Jsons;
import org.zhiqim.kernel.logging.Log;
import org.zhiqim.kernel.logging.LogFactory;
import org.zhiqim.kernel.util.Amounts;
import org.zhiqim.kernel.util.Validates;

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.AlipayConstants;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeCloseModel;
import com.alipay.api.domain.AlipayTradeFastpayRefundQueryModel;
import com.alipay.api.domain.AlipayTradePagePayModel;
import com.alipay.api.domain.AlipayTradeQueryModel;
import com.alipay.api.domain.AlipayTradeRefundModel;
import com.alipay.api.domain.AlipayTradeWapPayModel;
import com.alipay.api.domain.ZhimaCustomerCertificationCertifyModel;
import com.alipay.api.domain.ZhimaCustomerCertificationInitializeModel;
import com.alipay.api.domain.ZhimaCustomerCertificationMaterialCertifyModel;
import com.alipay.api.domain.ZhimaCustomerCertificationQueryModel;
import com.alipay.api.domain.ZhimaCustomerEpCertificationCertifyModel;
import com.alipay.api.domain.ZhimaCustomerEpCertificationInitializeModel;
import com.alipay.api.domain.ZhimaCustomerEpCertificationQueryModel;
import com.alipay.api.request.AlipayTradeCloseRequest;
import com.alipay.api.request.AlipayTradeFastpayRefundQueryRequest;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.request.AlipayTradeWapPayRequest;
import com.alipay.api.request.ZhimaCustomerCertificationCertifyRequest;
import com.alipay.api.request.ZhimaCustomerCertificationInitializeRequest;
import com.alipay.api.request.ZhimaCustomerCertificationMaterialCertifyRequest;
import com.alipay.api.request.ZhimaCustomerCertificationQueryRequest;
import com.alipay.api.request.ZhimaCustomerEpCertificationCertifyRequest;
import com.alipay.api.request.ZhimaCustomerEpCertificationInitializeRequest;
import com.alipay.api.request.ZhimaCustomerEpCertificationQueryRequest;
import com.alipay.api.response.AlipayTradeCloseResponse;
import com.alipay.api.response.AlipayTradeFastpayRefundQueryResponse;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.alipay.api.response.AlipayTradeRefundResponse;
import com.alipay.api.response.ZhimaCustomerCertificationCertifyResponse;
import com.alipay.api.response.ZhimaCustomerCertificationInitializeResponse;
import com.alipay.api.response.ZhimaCustomerCertificationMaterialCertifyResponse;
import com.alipay.api.response.ZhimaCustomerCertificationQueryResponse;
import com.alipay.api.response.ZhimaCustomerEpCertificationCertifyResponse;
import com.alipay.api.response.ZhimaCustomerEpCertificationInitializeResponse;
import com.alipay.api.response.ZhimaCustomerEpCertificationQueryResponse;

/**
 * 支付宝服务，共六项配置
 *
 * @version v1.0.0 @author zouzhigang 2018-3-23 新建与整理
 */
public class AlipayServer extends Servicer implements AlipayConstants, ZacConstants
{
    private static final Log log = LogFactory.getLog(AlipayServer.class);
    
    //六项配置
    private String apiUrl = ALIPAY_API_URL;    //支付宝网关
    private String appId;               //应用编号
    private String appPrivateKey;       //应用私钥
    private String alipayPublicKey;     //支付宝公钥
    private String notifyUrl;           //支付宝服务器主动通知商户服务器里指定的页面http/https路径
    private String returnUrl;           //同步返回地址
    
    //支付宝客户端
    private AlipayClient alipayClient;
    
    /****************************************************************************************/
    //获取属性
    /****************************************************************************************/
    
    public boolean create() throws Exception
    {
        Group group = Global.getGroup(id);
        if (group == null)
        {
            log.error("支付宝服务[%s]未找到配置", id);
            return false;
        }
        
        this.apiUrl = group.getString("apiUrl", ALIPAY_API_URL);
        this.appId = group.getString("appId");
        if (Validates.isEmpty(this.appId))
        {
            log.error("支付宝服务[%s][appId]不能为空", id);
            return false;
        }
        
        this.appPrivateKey = group.getString("appPrivateKey");
        if (Validates.isEmpty(this.appPrivateKey))
        {
            log.error("支付宝服务[%s][appPrivateKey]不能为空", id);
            return false;
        }
        
        this.alipayPublicKey = group.getString("alipayPublicKey");
        if (Validates.isEmpty(this.alipayPublicKey))
        {
            log.error("支付宝服务[%s][alipayPublicKey]不能为空", id);
            return false;
        }
        
        this.notifyUrl = group.getString("notifyUrl");
        if (Validates.isEmpty(this.notifyUrl))
        {
            log.error("支付宝服务[%s][notifyUrl]不能为空", id);
            return false;
        }
        
        this.returnUrl = group.getString("returnUrl");
        if (Validates.isEmpty(this.returnUrl))
        {
            log.error("支付宝服务[%s][notifyUrl]不能为空", id);
            return false;
        }
        
        //接口调用配置初始化一次，参数返回格式，只支持json，编码集，支持GBK/UTF-8，商户生成签名字符串所使用的签名算法类型，目前支持RSA2和RSA，推荐使用RSA2
        this.alipayClient = new DefaultAlipayClient(apiUrl, appId, appPrivateKey, FORMAT_JSON, CHARSET_UTF8, alipayPublicKey, SIGN_TYPE_RSA2);
        return true;
    }
    
    /****************************************************************************************/
    //支付&退款&查询等API
    /****************************************************************************************/
    
    /**
     * 电脑网站下单并支付
     * https://docs.open.alipay.com/270/alipay.trade.page.pay
     * 
     * @param tradeId               交易编号，64个字符以内、可包含字母、数字、下划线；需保证在商户端不重复
     * @param tradeAmount           交易金额，单位为元，精确到小数点后两位，取值范围[0.01,100000000]
     * @param tradeSubject          交易标题
     * @return                      生成的调用表单
     * @throws AlipayApiException   支付宝API异常
     */
    public String tradePagePay(long tradeId, int tradeAmount, String tradeSubject) throws AlipayApiException
    {
        //请求参数
        AlipayTradePagePayModel bizModel = new AlipayTradePagePayModel();
        bizModel.setOutTradeNo(""+tradeId);
        bizModel.setProductCode("FAST_INSTANT_TRADE_PAY");//销售产品码，与支付宝签约的产品码名称。 注：目前仅支持FAST_INSTANT_TRADE_PAY
        bizModel.setTotalAmount(Amounts.toYuan(tradeAmount));
        bizModel.setSubject(tradeSubject);
        
        AlipayTradePagePayRequest req = new AlipayTradePagePayRequest();
        req.setBizModel(bizModel);
        req.setReturnUrl(returnUrl);
        req.setNotifyUrl(notifyUrl);
        
        return alipayClient.pageExecute(req).getBody(); //调用SDK生成表单
    }
    
    /**
     * 手机网站下单并支付
     * https://docs.open.alipay.com/203/105288/
     * 
     * @param tradeId               交易编号，64个字符以内、可包含字母、数字、下划线；需保证在商户端不重复
     * @param tradeAmount           交易金额，单位为元，精确到小数点后两位，取值范围[0.01,100000000]
     * @param tradeSubject          交易标题
     * @return                      生成的调用表单
     * @throws AlipayApiException   支付宝API异常
     */
    public String tradeWapPay(long tradeId, int tradeAmount, String tradeSubject) throws AlipayApiException
    {
        //请求参数
        AlipayTradeWapPayModel bizModel = new AlipayTradeWapPayModel();
        bizModel.setOutTradeNo(""+tradeId);
        bizModel.setProductCode("QUICK_WAP_WAY");//销售产品码，与支付宝签约的产品码名称。 注：目前仅支持FAST_INSTANT_TRADE_PAY
        bizModel.setTotalAmount(Amounts.toYuan(tradeAmount));
        bizModel.setSubject(tradeSubject);
        
        AlipayTradeWapPayRequest req = new AlipayTradeWapPayRequest();
        req.setBizModel(bizModel);
        req.setReturnUrl(returnUrl);
        req.setNotifyUrl(notifyUrl);
        
        return alipayClient.pageExecute(req).getBody(); //调用SDK生成表单
    }
    
    /**
     * 统一收单交易退款接口
     * https://docs.open.alipay.com/api_1/alipay.trade.refund
     * 
     * @param tradeId               交易编号，订单支付时传入的商户订单号,不能和 trade_no同时为空
     * @param tradeNo               支付宝交易号，和商户订单号不能同时为空
     * @param refundAmount          需要退款的金额，该金额不能大于订单金额,单位为元，支持两位小数 
     * @param refundReason          退款的原因说明 可以为空
     * @param operatorId            商户的操作员编号 可以为空
     * @return                      标识一次退款请求，同一笔交易多次退款需要保证唯一，如需部分退款，则此参数必传。
     * @throws AlipayApiException   支付宝API异常
     */
    public AlipayTradeRefundResponse tradeRefund(long tradeId, String tradeNo, long refundTradeId, int refundAmount, String refundReason, String operatorId) throws AlipayApiException
    {
        //请求参数
        AlipayTradeRefundModel bizModel = new AlipayTradeRefundModel();
        bizModel.setOutTradeNo(""+tradeId);
        bizModel.setTradeNo(tradeNo);
        bizModel.setRefundAmount(Amounts.toYuan(refundAmount));
        bizModel.setRefundReason(refundReason);
        bizModel.setOperatorId(operatorId);
        bizModel.setOutRequestNo(""+refundTradeId);
        
        AlipayTradeRefundRequest req = new AlipayTradeRefundRequest();
        req.setBizModel(bizModel);
        req.setReturnUrl(returnUrl);
        req.setNotifyUrl(notifyUrl);
        
        return alipayClient.execute(req);
    }
    
    /**
     * 统一收单交易退款查询
     * https://docs.open.alipay.com/api_1/alipay.trade.fastpay.refund.query
     * 
     * @param tradeNo               支付宝交易号，和商户订单号不能同时为空
     * @param outTradeNo            订单支付时传入的商户订单号,和支付宝交易号不能同时为空。 trade_no,out_trade_no如果同时存在优先取trade_no
     * @param outRequestNo          请求退款接口时，传入的退款请求号，如果在退款请求时未传入，则该值为创建交易时的外部交易号
     * @return
     * @throws AlipayApiException
     */
    public AlipayTradeFastpayRefundQueryResponse tradeFastpayRefundQuery(String tradeNo, String outTradeNo, String outRequestNo)throws AlipayApiException
    {
        //请求参数
        AlipayTradeFastpayRefundQueryModel bizModel = new AlipayTradeFastpayRefundQueryModel();
        bizModel.setTradeNo(tradeNo);
        bizModel.setOutTradeNo(outTradeNo);
        bizModel.setOutRequestNo(outRequestNo);
        
        AlipayTradeFastpayRefundQueryRequest req = new AlipayTradeFastpayRefundQueryRequest();
        req.setBizModel(bizModel);
        req.setReturnUrl(returnUrl);
        req.setNotifyUrl(notifyUrl);
        
        return alipayClient.execute(req);
    }
    
    /**
     * 统一收单线下交易查询
     * https://docs.open.alipay.com/api_1/alipay.trade.query
     * 
     * @param outTradeNo            订单支付时传入的商户订单号,和支付宝交易号不能同时为空。trade_no,out_trade_no如果同时存在优先取trade_no 
     * @param tradeNo               支付宝交易号，和商户订单号不能同时为空
     * @return
     * @throws AlipayApiException
     */
    public AlipayTradeQueryResponse tradeQuery(long tradeId, String tradeNo) throws AlipayApiException
    {
        //请求参数
        AlipayTradeQueryModel bizModel = new AlipayTradeQueryModel();
        bizModel.setOutTradeNo(""+tradeId);
        bizModel.setTradeNo(tradeNo);
        
        AlipayTradeQueryRequest req = new AlipayTradeQueryRequest();
        req.setBizModel(bizModel);
        req.setReturnUrl(returnUrl);
        req.setNotifyUrl(notifyUrl);
        
        return alipayClient.execute(req);
    }
    
    /**
     * 统一收单交易关闭接口
     * https://docs.open.alipay.com/api_1/alipay.trade.close
     * 
     * @param tradeNo               该交易在支付宝系统中的交易流水号。最短 16 位，最长 64 位。和out_trade_no不能同时为空，如果同时传了 out_trade_no和 trade_no，则以 trade_no为准。
     * @param outTradeNo            订单支付时传入的商户订单号,和支付宝交易号不能同时为空。 trade_no,out_trade_no如果同时存在优先取trade_no
     * @param operatorId            卖家端自定义的的操作员 ID 可以为空
     * @return
     * @throws AlipayApiException
     */
    public AlipayTradeCloseResponse tradeClose(String tradeNo, String outTradeNo, String operatorId) throws AlipayApiException
    {
        AlipayTradeCloseRequest alipayRequest = new AlipayTradeCloseRequest();
        alipayRequest.setReturnUrl(returnUrl);
        alipayRequest.setNotifyUrl(notifyUrl);
        
        //请求参数
        AlipayTradeCloseModel bizModel = new AlipayTradeCloseModel();
        bizModel.setTradeNo(tradeNo);
        bizModel.setOutTradeNo(outTradeNo);
        bizModel.setOperatorId(operatorId);
        alipayRequest.setBizModel(bizModel);
        
        return alipayClient.execute(alipayRequest);
    }
    
    /****************************************************************************************/
    //芝麻信用API
    /****************************************************************************************/
    
    /**
     * 芝麻认证初始化
     * https://docs.open.alipay.com/api_8/zhima.customer.certification.initialize
     * 
     * @param transactionId         商户请求的唯一标志，商户要保证其唯一性。值为32位长度的字母数字下划线组合
     * @param certName              证件名
     * @param certNo                证件号
     * @return
     * @throws AlipayApiException
     */
    public ZhimaCustomerCertificationInitializeResponse certificationInit(String transactionId, String certName, String certNo) throws AlipayApiException
    {
        ZhimaCustomerCertificationInitializeRequest alipayRequest = new ZhimaCustomerCertificationInitializeRequest();
        
        ZhimaCustomerCertificationInitializeModel bizModel = new ZhimaCustomerCertificationInitializeModel();
        bizModel.setTransactionId(transactionId);
        bizModel.setProductCode("w1010100000000002978");
        bizModel.setBizCode("FACE");
        
        Map<String, String> identityParamMap = new HashMap<>();
        identityParamMap.put("identity_type", "CERT_INFO");
        identityParamMap.put("cert_type", "IDENTITY_CARD");
        identityParamMap.put("cert_name", certName);
        identityParamMap.put("cert_no", certNo);
        bizModel.setIdentityParam(Jsons.toString(identityParamMap));
        
        alipayRequest.setBizModel(bizModel);
        
        return alipayClient.execute(alipayRequest);
    }

    /**
     * 芝麻认证开始认证
     * https://docs.open.alipay.com/api_8/zhima.customer.certification.certify
     * 
     * @param bizNo             一次认证的唯一标识，在完成芝麻认证初始化后可以获取
     * @return
     * @throws AlipayApiException
     */
    public ZhimaCustomerCertificationCertifyResponse certificationCertify(String bizNo) throws AlipayApiException
    {
        ZhimaCustomerCertificationCertifyRequest alipayRequest = new ZhimaCustomerCertificationCertifyRequest();
        
        ZhimaCustomerCertificationCertifyModel bizModel = new ZhimaCustomerCertificationCertifyModel();
        bizModel.setBizNo(bizNo);
        alipayRequest.setBizModel(bizModel);
        alipayRequest.setReturnUrl("https://www.zhiqim.com");
        
        return alipayClient.pageExecute(alipayRequest, "GET");
    }
    
    /**
     * 芝麻认证查询
     * https://docs.open.alipay.com/api_8/zhima.customer.certification.query
     * 
     * @param bizNo             一次认证的唯一标识，在完成芝麻认证初始化后可以获取
     * @return
     * @throws AlipayApiException
     */
    public ZhimaCustomerCertificationQueryResponse certificationQuery(String bizNo) throws AlipayApiException
    {
        ZhimaCustomerCertificationQueryRequest alipayRequest = new ZhimaCustomerCertificationQueryRequest();
        
        ZhimaCustomerCertificationQueryModel bizModel = new ZhimaCustomerCertificationQueryModel();
        bizModel.setBizNo(bizNo);
        alipayRequest.setBizModel(bizModel);
        
        return alipayClient.execute(alipayRequest);
    }
    
    /**
     * 芝麻认证材料认证
     * https://docs.open.alipay.com/api_8/zhima.customer.certification.material.certify
     * 
     * @param transactionId         商户请求的唯一标志，商户要保证其唯一性。值为32位长度的字母数字下划线组合
     * @param productCode           芝麻认证产品码
     * @param identityParam         认证参数
     * @param materials             认证过程中需要的认证材料，不同认证场景需要的材料不同，biz_code值为FACE_API时需要材料FACIAL_PICTURE_FRONT
     * @return
     * @throws AlipayApiException
     */
    public ZhimaCustomerCertificationMaterialCertifyResponse certificationMaterialCertify(String transactionId, String productCode, String identityParam, String materials) throws AlipayApiException
    {
        ZhimaCustomerCertificationMaterialCertifyRequest alipayRequest = new ZhimaCustomerCertificationMaterialCertifyRequest();
        
        ZhimaCustomerCertificationMaterialCertifyModel bizModel = new ZhimaCustomerCertificationMaterialCertifyModel();
        bizModel.setTransactionId(transactionId);
        bizModel.setProductCode(productCode);
        bizModel.setBizCode("FACE_API");
        bizModel.setIdentityParam(identityParam);
        alipayRequest.setBizModel(bizModel);
        
        return alipayClient.execute(alipayRequest);
    }
    
    /**
     * 企业认证初始化
     * https://docs.open.alipay.com/api_8/zhima.customer.ep.certification.initialize
     * 
     * @param transactionId         商户请求的唯一标志，商户要保证其唯一性。值为32位长度的字母数字下划线组合
     * @param certName              法人姓名
     * @param certNo                法人身份证号
     * @param epCertType            企业认证类型
     * @param epCertName            企业名
     * @param epCertNo              企业号
     * @return
     * @throws AlipayApiException
     */
    public ZhimaCustomerEpCertificationInitializeResponse epCertificationInit(String transactionId, String certName, String certNo, String epCertType, String epCertName, String epCertNo) throws AlipayApiException
    {
        ZhimaCustomerEpCertificationInitializeRequest alipayRequest = new ZhimaCustomerEpCertificationInitializeRequest();
        
        ZhimaCustomerEpCertificationInitializeModel bizModel = new ZhimaCustomerEpCertificationInitializeModel();
        bizModel.setTransactionId(transactionId);
        bizModel.setProductCode("w1010100003000001889");
        bizModel.setBizCode("EP_ALIPAY_ACCOUNT");
        
        Map<String, String> identityParamMap = new HashMap<>();
        identityParamMap.put("identity_type", "EP_CERT_INFO");
        identityParamMap.put("cert_type", "IDENTITY_CARD");
        identityParamMap.put("cert_name", certName);
        identityParamMap.put("cert_no", certNo);
        identityParamMap.put("ep_cert_type", epCertType); //NATIONAL_LEGAL 工商注册号, NATIONAL_LEGAL_MERGE 社会统一信用代码
        identityParamMap.put("ep_cert_name", epCertName);
        identityParamMap.put("ep_cert_no", epCertNo);
        bizModel.setIdentityParam(Jsons.toString(identityParamMap));
        
        alipayRequest.setBizModel(bizModel);
        
        return alipayClient.execute(alipayRequest);
    }
    
    /**
     * 企业认证引导(页面接口)
     * https://docs.open.alipay.com/api_8/zhima.customer.ep.certification.certify
     * 
     * @param bizNo             一次认证的唯一标识，在完成芝麻认证初始化后可以获取
     * @return
     * @throws AlipayApiException
     */
    public ZhimaCustomerEpCertificationCertifyResponse epCertificationCertify(String bizNo) throws AlipayApiException
    {
        ZhimaCustomerEpCertificationCertifyRequest alipayRequest = new ZhimaCustomerEpCertificationCertifyRequest();
        
        ZhimaCustomerEpCertificationCertifyModel bizModel = new ZhimaCustomerEpCertificationCertifyModel();
        bizModel.setBizNo(bizNo);
        alipayRequest.setBizModel(bizModel);
        alipayRequest.setReturnUrl("https://www.zhiqim.com/zhimaEpCertifyReturn.htm");
        
        return alipayClient.pageExecute(alipayRequest, "GET");
    }
    
    /**
     * 企业认证查询服务
     * https://docs.open.alipay.com/api_8/zhima.customer.ep.certification.query
     * 
     * @param bizNo             一次认证的唯一标识，在完成芝麻认证初始化后可以获取
     * @return
     * @throws AlipayApiException
     */
    public ZhimaCustomerEpCertificationQueryResponse epCertificationQuery(String bizNo) throws AlipayApiException
    {
        ZhimaCustomerEpCertificationQueryRequest alipayRequest = new ZhimaCustomerEpCertificationQueryRequest();
        
        ZhimaCustomerEpCertificationQueryModel bizModel = new ZhimaCustomerEpCertificationQueryModel();
        bizModel.setBizNo(bizNo);
        alipayRequest.setBizModel(bizModel);
        
        return alipayClient.execute(alipayRequest);
    }
    
    /****************************************************************************************/
    //获取属性
    /****************************************************************************************/
    
    public String getAppId()
    {
        return appId;
    }

    public String getServerUrl()
    {
        return apiUrl;
    }

    public String getAppPrivateKey()
    {
        return appPrivateKey;
    }

    public String getAlipayPublicKey()
    {
        return alipayPublicKey;
    }

    public String getNotifyUrl()
    {
        return notifyUrl;
    }

    public String getReturnUrl()
    {
        return returnUrl;
    }
}
